/*
 * 描述： <描述>
 * 修改人： rain
 * 修改时间： 2016年9月20日
 * 项目： rainhy-auth
 */
package com.rainhy.auth.authloader.annotation;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import com.rainhy.auth.context.AuthItem;
import com.rainhy.auth.context.AuthItemGroup;
import com.rainhy.auth.context.AuthLoader;
import com.rainhy.core.exception.RainhyException;
import com.rainhy.core.util.AssertUtils;
import com.rainhy.webcore.util.ProxyUtils;

/**
 * 注解权限加载器
 * 
 * @author rain
 * @version [版本号, 2016年9月20日]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
public class AnnotationAuthLoader implements AuthLoader, ApplicationContextAware {
    
    private ApplicationContext applicationContext;
    
    @Override
    public Collection<AuthItemGroup> load() {
        MultiValueMap<AuthItemGroup, AuthItem> authMap = loadAuth();
        
        Map<String, AuthItemGroup> key2AuthItemGroupMapping = new HashMap<>();
        for (Map.Entry<AuthItemGroup, List<AuthItem>> entry : authMap.entrySet()) {
            AuthItemGroup authItemGroup = entry.getKey();
            key2AuthItemGroupMapping.put(authItemGroup.getKey(), authItemGroup);
        }
        
        for (Map.Entry<AuthItemGroup, List<AuthItem>> entry : authMap.entrySet()) {
            for (AuthItem authItem : entry.getValue()) {
                adjustAuthItem(entry.getKey(), authItem);
                adjustAuthItemGroup(key2AuthItemGroupMapping, entry.getKey());
            }
        }
        return authMap.keySet();
    }
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    
    /**
     * 梳理权限项关系
     *
     * @param key
     * @param auth
     * 
     * @return void [返回类型说明]
     * @throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年9月28日]
     * @author rain
     */
    private void adjustAuthItem(AuthItemGroup authItemGroup, AuthItem auth) {
        AnnAuthItemGroup parentAuthItemGroup = (AnnAuthItemGroup) authItemGroup;
        AnnAuthItem authItem = (AnnAuthItem) auth;
        
        parentAuthItemGroup.addAuthItemChild(authItem);
        authItem.setGroup(parentAuthItemGroup);
    }
    
    /**
     * 梳理权限组关系
     *
     * @param key
     * 
     * @return void [返回类型说明]
     * @throws [异常类型] [异常说明]
     * @see [类、类#方法、类#成员]
     * @version [版本号, 2016年9月28日]
     * @author rain
     */
    private void adjustAuthItemGroup(Map<String, AuthItemGroup> key2AuthItemGroupMapping, AuthItemGroup authItemGroup) {
        AnnAuthItemGroup aaig = (AnnAuthItemGroup) authItemGroup;
        AnnAuthItemGroup parentAuthItemGroup = (AnnAuthItemGroup) key2AuthItemGroupMapping.get(aaig.getParentKey());
        if (parentAuthItemGroup != null) {
            parentAuthItemGroup.addChild(aaig);
            aaig.setParent(parentAuthItemGroup);
        }
    }
    
    /** 加载权限组和权限 */
    private MultiValueMap<AuthItemGroup, AuthItem> loadAuth() {
        MultiValueMap<AuthItemGroup, AuthItem> authMap = new LinkedMultiValueMap<>();
        Map<String, Object> beansMap = this.applicationContext.getBeansWithAnnotation(AuthGroup.class);
        if (MapUtils.isEmpty(beansMap)) {
            return authMap;
        }
        
        Map<String, AuthGroup> authGroupMapTemp = new HashMap<>();
        for (Object beanTemp : beansMap.values()) {
            Object target = beanTemp;
            try {
                target = ProxyUtils.getTarget(beanTemp);
            } catch (Exception e) {
                throw new RainhyException(e, "获取代理对象异常");
            }
            Class<? extends Object> targetClass = target.getClass();
            Method[] methods = targetClass.getMethods();
            if (ArrayUtils.isEmpty(methods)) {
                continue;
            }
            
            AuthGroup annAuthGroup = targetClass.getAnnotation(AuthGroup.class);
            String groupKey = StringUtils.isBlank(annAuthGroup.key()) ? targetClass.getName() : annAuthGroup.key();
            String groupName = annAuthGroup.value();
            String groupParentKey = annAuthGroup.parentKey();
            
            AuthGroup authGroupTemp = authGroupMapTemp.get(groupKey);
            if (authGroupTemp != null) {
                AssertUtils.equals(authGroupTemp.parentKey(), annAuthGroup.parentKey(), "发现存在 key 相同但 parentKey 不相同的权限组");
                AssertUtils.equals(authGroupTemp.value(), annAuthGroup.value(), "发现存在 key 相同但 name 不相同的权限组");
            } else {
                authGroupMapTemp.put(groupKey, annAuthGroup);
            }
            
            AnnAuthItemGroup aaig = new AnnAuthItemGroup(groupKey, groupName, groupParentKey);
            for (Method methodTemp : methods) {
                // 检查是否存在Auth注解
                if (!methodTemp.isAnnotationPresent(Auth.class)) {
                    continue;
                }
                Auth annAuth = methodTemp.getAnnotation(Auth.class);
                String authKey = StringUtils.isBlank(annAuth.key()) ? groupKey + "." + methodTemp.getName() : annAuth.key();
                authMap.add(aaig, new AnnAuthItem(authKey, annAuth.value(), annAuth.description()));
            }
        }
        return authMap;
    }
    
}
